"""
API middleware following SOLID principles.
"""

import time
import uuid
from typing import Callable, Optional, Dict, Any
from fastapi import Request, Response
from fastapi.middleware.base import BaseHTTPMiddleware
from starlette.middleware.base import RequestResponseEndpoint

from ..logging import get_logger
from ..cache import cache_service
from .exceptions import RateLimitError


class APIMiddleware(BaseHTTPMiddleware):
    """Base API middleware."""
    
    def __init__(self, app, logger_name: str = "APIMiddleware"):
        super().__init__(app)
        self.logger = get_logger(logger_name)
    
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        """Dispatch request through middleware."""
        # Add request ID if not present
        request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())
        request.state.request_id = request_id
        
        # Log request
        start_time = time.time()
        self.logger.info(
            "Request started",
            method=request.method,
            url=str(request.url),
            request_id=request_id,
            client_ip=request.client.host if request.client else None
        )
        
        try:
            # Process request
            response = await call_next(request)
            
            # Add request ID to response headers
            response.headers["X-Request-ID"] = request_id
            
            # Log response
            duration = time.time() - start_time
            self.logger.info(
                "Request completed",
                method=request.method,
                url=str(request.url),
                status_code=response.status_code,
                duration=duration,
                request_id=request_id
            )
            
            return response
            
        except Exception as exc:
            duration = time.time() - start_time
            self.logger.error(
                "Request failed",
                method=request.method,
                url=str(request.url),
                error=str(exc),
                duration=duration,
                request_id=request_id,
                exc_info=True
            )
            raise


class RateLimitMiddleware(BaseHTTPMiddleware):
    """Rate limiting middleware."""
    
    def __init__(
        self,
        app,
        requests_per_minute: int = 60,
        burst_size: int = 10,
        key_func: Optional[Callable[[Request], str]] = None
    ):
        super().__init__(app)
        self.requests_per_minute = requests_per_minute
        self.burst_size = burst_size
        self.key_func = key_func or self._default_key_func
        self.logger = get_logger("RateLimitMiddleware")
    
    def _default_key_func(self, request: Request) -> str:
        """Default key function using client IP."""
        client_ip = request.client.host if request.client else "unknown"
        return f"rate_limit:{client_ip}"
    
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        """Apply rate limiting."""
        # Skip rate limiting for health checks
        if request.url.path in ["/health", "/metrics"]:
            return await call_next(request)
        
        key = self.key_func(request)
        
        try:
            # Check rate limit
            current_requests = await self._get_current_requests(key)
            
            if current_requests >= self.requests_per_minute:
                self.logger.warning(
                    "Rate limit exceeded",
                    key=key,
                    current_requests=current_requests,
                    limit=self.requests_per_minute
                )
                raise RateLimitError(
                    detail="Rate limit exceeded",
                    retry_after=60
                )
            
            # Increment counter
            await self._increment_requests(key)
            
            return await call_next(request)
            
        except RateLimitError:
            raise
        except Exception as exc:
            self.logger.error(
                "Rate limit check failed",
                key=key,
                error=str(exc),
                exc_info=True
            )
            # Continue without rate limiting on error
            return await call_next(request)
    
    async def _get_current_requests(self, key: str) -> int:
        """Get current request count."""
        try:
            count = await cache_service.get(key)
            return int(count) if count else 0
        except Exception:
            return 0
    
    async def _increment_requests(self, key: str):
        """Increment request count."""
        try:
            current = await self._get_current_requests(key)
            await cache_service.set(key, current + 1, expire=60)
        except Exception as exc:
            self.logger.error(
                "Failed to increment rate limit counter",
                key=key,
                error=str(exc)
            )


class CORSMiddleware(BaseHTTPMiddleware):
    """CORS middleware."""
    
    def __init__(
        self,
        app,
        allow_origins: list = None,
        allow_methods: list = None,
        allow_headers: list = None,
        allow_credentials: bool = False,
        max_age: int = 86400
    ):
        super().__init__(app)
        self.allow_origins = allow_origins or ["*"]
        self.allow_methods = allow_methods or ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
        self.allow_headers = allow_headers or ["*"]
        self.allow_credentials = allow_credentials
        self.max_age = max_age
    
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        """Handle CORS."""
        origin = request.headers.get("Origin")
        
        # Handle preflight requests
        if request.method == "OPTIONS":
            response = Response()
            self._add_cors_headers(response, origin)
            return response
        
        # Process request
        response = await call_next(request)
        self._add_cors_headers(response, origin)
        
        return response
    
    def _add_cors_headers(self, response: Response, origin: Optional[str]):
        """Add CORS headers to response."""
        if origin and (self.allow_origins == ["*"] or origin in self.allow_origins):
            response.headers["Access-Control-Allow-Origin"] = origin
        elif self.allow_origins == ["*"]:
            response.headers["Access-Control-Allow-Origin"] = "*"
        
        response.headers["Access-Control-Allow-Methods"] = ", ".join(self.allow_methods)
        response.headers["Access-Control-Allow-Headers"] = ", ".join(self.allow_headers)
        response.headers["Access-Control-Max-Age"] = str(self.max_age)
        
        if self.allow_credentials:
            response.headers["Access-Control-Allow-Credentials"] = "true"


class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    """Security headers middleware."""
    
    def __init__(self, app):
        super().__init__(app)
        self.security_headers = {
            "X-Content-Type-Options": "nosniff",
            "X-Frame-Options": "DENY",
            "X-XSS-Protection": "1; mode=block",
            "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
            "Referrer-Policy": "strict-origin-when-cross-origin",
            "Content-Security-Policy": "default-src 'self'"
        }
    
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        """Add security headers."""
        response = await call_next(request)
        
        for header, value in self.security_headers.items():
            response.headers[header] = value
        
        return response


class CompressionMiddleware(BaseHTTPMiddleware):
    """Response compression middleware."""
    
    def __init__(self, app, minimum_size: int = 1024):
        super().__init__(app)
        self.minimum_size = minimum_size
    
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        """Apply compression if supported."""
        response = await call_next(request)
        
        # Check if client supports compression
        accept_encoding = request.headers.get("Accept-Encoding", "")
        
        if "gzip" in accept_encoding and self._should_compress(response):
            # Implement compression logic here
            # This is a placeholder - you'd use a proper compression library
            response.headers["Content-Encoding"] = "gzip"
        
        return response
    
    def _should_compress(self, response: Response) -> bool:
        """Check if response should be compressed."""
        content_type = response.headers.get("Content-Type", "")
        content_length = response.headers.get("Content-Length")
        
        # Only compress text-based content
        compressible_types = [
            "application/json",
            "application/javascript",
            "text/html",
            "text/css",
            "text/plain"
        ]
        
        if not any(ct in content_type for ct in compressible_types):
            return False
        
        # Only compress if content is large enough
        if content_length and int(content_length) < self.minimum_size:
            return False
        
        return True


class RequestSizeLimitMiddleware(BaseHTTPMiddleware):
    """Request size limit middleware."""
    
    def __init__(self, app, max_size: int = 10 * 1024 * 1024):  # 10MB default
        super().__init__(app)
        self.max_size = max_size
        self.logger = get_logger("RequestSizeLimitMiddleware")
    
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        """Check request size limit."""
        content_length = request.headers.get("Content-Length")
        
        if content_length and int(content_length) > self.max_size:
            self.logger.warning(
                "Request size limit exceeded",
                content_length=content_length,
                max_size=self.max_size,
                url=str(request.url)
            )
            from .exceptions import APIException
            raise APIException(
                status_code=413,
                detail="Request entity too large",
                error_code="REQUEST_TOO_LARGE"
            )
        
        return await call_next(request)